﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data.SqlServerCe;
using System.Data;
using OverStore.Configuration.Builder.Fluent;
using OverStore.Exceptions;
using OverStore.Configuration;
using OverStore.Scoping;
using OverStore.Configuration.Components;
using OverStore.Runtime.Storage;
using OverStore.Configuration.Builder;
using OverStore.Configuration.Storage;
using OverStore.Sql.TableSchema;
using System.Reflection;

namespace OverStore.TutorialAsTest.UnitTest.Configuration.Rdbms.InstanceStorage
{
    [TestClass]
    public class SimpleClassStorage_Test
    {
        protected OverStoreConfiguration CreateConfiguration(string connectionString)
        {
            return OverStoreFluent.Configure()
                .LogToConsole()
                .ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<SimpleClass, Guid>()
                    .WithDepot(Component.Depot.WithInternalKey<SimpleClass, Guid>("Id"))
                    .WithMapper(CreateSimpleClassMapper())
                    .WithCommands(Component.CommandSet.FromTable(Component.CreateTable("SimpleClass")
                                                                            .Key<Guid>("ID")
                                                                            .Column<String>("Name")
                                                                            .Column<DateTime>("CreationDate")
                                                                            .Column<int>("Type"), false))
                    .WithChangesTracker(Component.InstanceChangesTracker.Checksum<SimpleClass, Guid>("ID", "Name", "CreationDate", "Type"))
                    .ByIdentifierAttaching(c => c.ID)
                .EndObjectRepository()
            .End();
        }

        private static IComponentConfiguration<OverStore.Runtime.ObjectRepository.Mapping.IMapper<SimpleClass>> CreateSimpleClassMapper()
        {
            return Component.Mapper.Delegate<SimpleClass>(
                (item, name, p) =>
                {
                    switch (name)
                    {
                        case "ID":
                        case "Id":
                            return item.ID;
                        case "Name":
                            return item.Name;
                        case "CreationDate":
                            return item.CreationDate;
                        case "Type":
                            return item.Type;
                        case "orig__ID":
                            return p.GetIdentifier<SimpleClass, Guid>(item);
                        default:
                            return Missing.Value;
                    }
                },
                (data, item, p) =>
                {
                    if (data.ContainsName("Name"))
                        item.Name = data.GetString("Name");
                    if (data.ContainsName("CreationDate"))
                        item.CreationDate = data.GetDateTime("CreationDate");
                    if (data.ContainsName("Type"))
                        item.Type = (SimpleClassType)data.GetInt32("Type");
                });
        }

        protected OverStoreConfiguration CreateConfigurationWithPartialUpdate(string connectionString)
        {
            var table = Component.CreateTable("SimpleClass").Key<Guid>("ID").Column<string>("Name").Column<DateTime>("CreationDate").Column<int>("Type");
            var commandSet = Component.CommandSet.FromTable(table, false);
            commandSet.CustomUpdate = Component.AdoCommand.WriteCommand("update SimpleClass set CreationDate = @CreationDate where Id = @Id")
                                             .InputParameter("CreationDate", DbType.DateTime)
                                             .InputParameter("Id", DbType.Guid);

            return OverStoreFluent.Configure()
                    .LogToConsole()
                    .ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                    .CustomClassRepository<SimpleClass, Guid>()
                        .WithDepot(Component.Depot.WithInternalKey<SimpleClass, Guid>("Id"))
                        .WithMapper(CreateSimpleClassMapper())
                        .WithCommands(commandSet)
                        .WithChangesTracker(Component.InstanceChangesTracker.Checksum<SimpleClass, Guid>("ID", "Name", "CreationDate", "Type"))
                        .ByIdentifierAttaching(i => i.ID)
                    .EndObjectRepository()
                .End();
        }

        #region GetByIdentifier object

        [TestMethod]
        public void InstanceStorage_GetObject_CorrectContent()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (IPersistenceSession session = CreateConfiguration(connectionString).CreateSession())
            {
                Guid id = new Guid("f30c02ce-33fd-4dda-8228-500609c8ee7b");
                SimpleClass instance = session.GetByIdentifier<SimpleClass, Guid>(id);
                Assert.AreEqual(id, instance.ID);
                Assert.AreEqual("NewClass", instance.Name);
                Assert.AreEqual(new DateTime(2008, 12, 15, 15, 29, 44).ToString(), instance.CreationDate.ToString());
                Assert.AreEqual(SimpleClassType.Default, instance.Type);
            }
        }

        [TestMethod]
        public void InstanceStorage_GetObject_IdentityMapping()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("f30c02ce-33fd-4dda-8228-500609c8ee7b");
                SimpleClass instance = session.GetByIdentifier<SimpleClass, Guid>(id);
                SimpleClass reference = session.GetByIdentifier<SimpleClass, Guid>(id);

                Assert.AreSame(instance, reference);
                using (IPersistenceSession secondSession = config.CreateSession())
                {
                    SimpleClass secondSessionBound = secondSession.GetByIdentifier<SimpleClass, Guid>(id);
                    Assert.AreNotSame(instance, secondSessionBound);
                    Assert.AreEqual(instance, secondSessionBound);
                }
            }
        }

        [TestMethod]
        [ExpectedException(typeof(PersistenceOperationFailedException))]
        public void InstanceStorage_GetObject_InstanceMissing()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("DE11C058-1D29-425b-AAFD-FDF91509E263");
                SimpleClass instance = session.GetByIdentifier<SimpleClass, Guid>(id);
            }
        }

        #endregion

        #region ReloadUsingData

        [TestMethod]
        public void InstanceStorage_ReloadObject()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("f30c02ce-33fd-4dda-8228-500609c8ee7b");
                SimpleClass instance = new SimpleClass()
                {
                    ID = id
                };
                session.Reload(instance);

                Assert.AreEqual(id, instance.ID);
                Assert.AreEqual("NewClass", instance.Name);
                Assert.AreEqual(SimpleClassType.Default, instance.Type);

                instance.Name = "22";
                session.Reload(instance);
                Assert.AreEqual("NewClass", instance.Name);

                Assert.AreSame(instance, session.GetByIdentifier<SimpleClass, Guid>(id));
            }
        }

        [TestMethod]
        public void InstanceStorage_ReloadObjectDoesNotExistInDatabase()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = Guid.NewGuid();
                SimpleClass instance = new SimpleClass()
                {
                    ID = id
                };
                Assert.IsFalse(session.Reload(instance));

                Assert.IsNull(session.GetByIdentifierOrDefault<SimpleClass, Guid>(id));

                instance.ID = id;
                session.Add(instance);

                Assert.IsFalse(session.Reload(instance));
                Assert.AreSame(instance, session.GetByIdentifierOrDefault<SimpleClass, Guid>(id));
            }
        }



        #endregion

        #region SaveAll object

        [TestMethod]
        public void InstanceStorage_SaveObject()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("f30c02ce-33fd-4dda-8228-500609c8ee7b");
                SimpleClass instance = session.GetByIdentifier<SimpleClass, Guid>(id);
                instance.Name = "ChangedName";
                session.Save(instance);
                using (IPersistenceSession second = config.CreateSession())
                {
                    SimpleClass reference = second.GetByIdentifier<SimpleClass, Guid>(id);
                    Assert.AreNotSame(instance, reference);
                    Assert.AreEqual(instance.Name, reference.Name);

                    reference.Name = "NewClass";
                    second.Save(reference);
                }

                session.Reload(instance);
                Assert.AreEqual("NewClass", instance.Name);
            }
        }

        [TestMethod]
        public void InstanceStorage_SaveObjectPartialUpdate()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfigurationWithPartialUpdate(connectionString);
            using (var session = config.CreateSession())
            {
                var id = new Guid("f30c02ce-33fd-4dda-8228-500609c8ee7b");
                var instance = session.GetByIdentifier<SimpleClass, Guid>(id);
                var creationDate = new DateTime(2999, 5, 5);
                instance.Name = "ChangedName";
                instance.CreationDate = creationDate;
                session.Save(instance);
                Assert.AreEqual("ChangedName", instance.Name);
                using (var second = config.CreateSession())
                {
                    var reference = second.GetByIdentifier<SimpleClass, Guid>(id);
                    Assert.AreNotSame(instance, reference);
                    Assert.AreEqual(instance.CreationDate, creationDate);
                }

                session.Reload(instance);
                Assert.AreEqual("NewClass", instance.Name);
            }
        }

        #endregion

        #region MarkAsDeleted

        [TestMethod]
        public void InstanceStorage_MarkObjectAsDeleted()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("8f10f298-b4a3-4686-ab7e-e565f8bee70a");
                SimpleClass instance = session.GetByIdentifier<SimpleClass, Guid>(id);
                session.MarkAsDeleted(instance);
                session.Reload(instance);
                session.Save(instance);
                using (IPersistenceSession second = config.CreateSession())
                {
                    var reference = second.GetByIdentifier<SimpleClass, Guid>(id);
                    Assert.AreEqual(instance.ID, reference.ID);
                }

                session.MarkAsDeleted(instance);
                session.Save(instance);
                bool isFailed = false;
                using (IPersistenceSession second = config.CreateSession())
                {
                    try
                    {
                        SimpleClass reference = second.GetByIdentifier<SimpleClass, Guid>(id);
                    }
                    catch (PersistenceOperationFailedException)
                    {
                        isFailed = true;
                    }
                    if (!isFailed)
                        Assert.Fail("Object does not deleted");
                    var simpleClass = new SimpleClass { ID = id, CreationDate = DateTime.Now, Name = "sdad", Type = SimpleClassType.Stub };
                    second.Save(simpleClass);
                }
            }
        }

        #endregion

        #region Object state management

        [TestMethod]
        public void InstanceStorage_AddObject()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("41297FDA-E4ED-4475-8C8E-04AD27EF6C73");

                using (IPersistenceSession secondSession = config.CreateSession())
                {
                    try
                    {

                        var instance = secondSession.GetByIdentifier<SimpleClass, Guid>(id);
                        Assert.Inconclusive("The SimpleClass with identifier {0} available in data source",
                            id);
                    }
                    catch (PersistenceOperationFailedException)
                    {
                        // Do nothing, normal behaviour
                    }
                }

                SimpleClass newInstance = new SimpleClass()
                {
                    ID = id,
                    Type = SimpleClassType.Stub,
                    Name = "Instance",
                    CreationDate = DateTime.Now
                };

                session.Add(newInstance);

                Assert.AreSame(newInstance, session.GetByIdentifier<SimpleClass, Guid>(id));

                //Assert.AreEqual(ObjectChangeState.Unknown, session.GetObjectState(newInstance));
            }
        }

        [TestMethod]
        [ExpectedException(typeof(IdentityReferenceIntegrityException))]
        public void InstanceStorage_AddObject_ReferenceIntegrityFails()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var config = CreateConfiguration(connectionString);
            using (IPersistenceSession session = config.CreateSession())
            {
                Guid id = new Guid("64ca46ee-de9e-4388-9a79-96f1a062fc52");
                SimpleClass instance = session.GetByIdentifier<SimpleClass, Guid>(id);

                SimpleClass newInstance = new SimpleClass()
                {
                    ID = id
                };
                session.Add(newInstance);
            }
        }

        #endregion
    }
}
